package com.ding.easycontroller.work;

import jdk.nashorn.api.scripting.ScriptObjectMirror;
import lombok.Data;

import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptException;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.Writer;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


@Data
public class Controller extends Script { //直接在这里进行工作??

    {
        setType("controller");
    }


    //接口地址
    private String uri;

    //请求方法
    private String method;

    //执行引擎
    private ScriptEngine scriptEngine;

    //controller方法的参数列表
    private String[] controllerFunctionParams;

    //用于本地调用的方法名称
    private String localFunctionName;

    //检查是否含有_controller方法的正则
    private static Pattern _controller_pattern = Pattern.compile("function[ \t\n]+_controller[ \t\n]*\\((.*)\\)");

    //ant风格路径
    private String regex;

    //带有路径的正则
    private String regexWithPathParams;

    //路径参数
    private List<String> pathParams;//对路径参数也要进行处理

    //用于提取路径占位符的正则
    private Pattern pathParamsPattern;

    //模糊比率,用于controller排序
    private double rate = 0;

    private String initJs;

    protected void doInit() {
        if (initJs == null) {
            preInit();
        }
        try {
            scriptEngine.eval(initJs);
        } catch (ScriptException e) {
            throw new RuntimeException(e);
        }

    }

    @Override
    protected void doDestroy() {
        try {
            scriptEngine.eval("delete " + localFunctionName);
        } catch (ScriptException e) {
            e.printStackTrace();
        }
    }

    private void preInit() {

        if (js == null) {
            js = "";
        }

        Matcher matcher = _controller_pattern.matcher(js);

        String paramS = "_req,_resp,_reqHeads,_reader,_writer,_input,_output,_session,_params,_pathValues,_respHeads";

        String invokeS = "";

        if (matcher.find()) {

            //从_controller方法中分离中请求参数
            String group = matcher.group(1);
            String[] split = group.split(",");
            if (split.length == 1) {
                if (split[0].trim().equals("")) {
                    split = new String[0];
                } else {
                    split[0] = split[0].trim();
                }
            } else {
                for (int i = 0; i < split.length; i++) {
                    split[i] = split[i].trim();
                }
            }
            controllerFunctionParams = split;
            invokeS = group;
            if (controllerFunctionParams.length > 0) {
                paramS = paramS + "," + group;
            }

        } else {
            throw new RuntimeException("必须有_controller方法");
        }

        localFunctionName = "__ctrl_" + id;

        //前置脚本
        String pre = "function " + localFunctionName + "(" + paramS + ")" + "{var _status;\n";

        //后置脚本
        String suf = "\n" +
                "return {data:_controller(" + invokeS + "),status:_status}}";

        //拼合完整脚本
        initJs = pre + js + suf;

        //生成路径正则
        if (pathParams != null) {

            //编译路径参数
            pathParamsPattern = Pattern.compile(regexWithPathParams);
        }

    }


    public Result handle(Object[] params) {

        Writer writer = (Writer) params[4];

        try {

            //调用controller方法获取运算结果
            Object obj = ((Invocable) scriptEngine).invokeFunction(localFunctionName, (Object[]) params);

            //强转运算结果
            ScriptObjectMirror r = (ScriptObjectMirror) obj;

            return new Result(r, scriptEngine);
        } catch (Exception e) {
            e.printStackTrace();

            try {
                writer.write(e.getMessage());
            } catch (IOException ignore) {
            }
            return null;
        }
    }


    public boolean equals(Object obj) {
        return super.equals(obj);
    }

    public Object[] prepare(Object[] base) {

        try {

            //请求参数
            HttpServletRequest request = (HttpServletRequest) base[0];

            ScriptObjectMirror reqParams = (ScriptObjectMirror) base[8];

            //准备路径参数
            ScriptObjectMirror pathVariables = (ScriptObjectMirror) ((Invocable) scriptEngine).invokeFunction("__createJson");
            if (pathParamsPattern != null) {
                String requestURI = request.getRequestURI();
                Matcher matcher = pathParamsPattern.matcher(requestURI);
                if (matcher.find()) {
                    for (String pathParam : pathParams) {
                        pathVariables.put(pathParam, matcher.group(pathParam));
                    }
                }
            }

            base[9] = pathVariables;


            //准备真实参数
            Object params[] = new Object[controllerFunctionParams.length + base.length];

            System.arraycopy(base, 0, params, 0, base.length);

            for (int i = 0; i < controllerFunctionParams.length; i++) {
                params[i + base.length] = reqParams.get(controllerFunctionParams[i]);
            }

            return params;

        } catch (Exception ignore) {
            return null;
        }

    }


    public static class Result {

        private Integer status;

        private String data;

        private Result(ScriptObjectMirror r, ScriptEngine scriptEngine) {
            //从运算结果中获取状态码
            Object status = r.get("status");

            if (status != null) {

                if (!(status instanceof Integer)) {
                    throw new RuntimeException("响应码必须为数字");
                }

                this.status = (int) status;
            }


            Object data = r.get("data");

            if (data != null) {
                //如果运算结果是一个json字符串,将字符串写入
                if (data instanceof ScriptObjectMirror) {
                    try {
                        this.data = (String) (((Invocable) scriptEngine).invokeFunction("__objToString", data));
                    } catch (Exception ignore) {
                    }
                } else {
                    this.data = data.toString();
                }
            }
        }

        public String getData() {
            return data;
        }

        public Integer getStatus() {
            return status;
        }
    }


}
